<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Show the path Dynamicly</title>
  </head>
  <style>
    :root {
      --FONT-SIZE: 32px;
      --STROKE-WIDTH: 4px;
      --C-WHITE: #f3f3f3;
      --C-BLACK: #333;
      --LENGTH-WIDTH: 200px;
      --LENGTH-HEIGHT: calc(var(--FONT-SIZE) + 8px);
      --PAD-TOP: calc(360px - (var(--FONT-SIZE) / 2));
      --PAD-LEFT: calc(700px - (var(--LENGTH-WIDTH) / 2));
    }

    body {
      background-color: var(--C-BLACK);
      font-family: Consolas, "Courier New", monospace;
    }

    #IPT_DIV {
      position: fixed;
      display: flex;
      place-content: center center;
      /*top: var(--PAD-TOP);*/
      /* left: var(--PAD-LEFT); */
      width: var(--LENGTH-WIDTH);
      height: var(--LENGTH-HEIGHT);
      background-color: var(--C-WHITE);
      visibility: hidden;
      text-align: center;

      & input {
        width: 100%;
        text-align: center;
        font-size: var(--FONT-SIZE);
      }
    }

    #SVG {
      display: block;
      margin: 0 auto;

      text {
        text-anchor: middle;
        font-size: var(--FONT-SIZE);
        fill: var(--C-WHITE);
      }

      line {
        stroke: var(--C-WHITE);
        stroke-width: var(--STROKE-WIDTH);
      }
    }
  </style>
  <body>
    <div id="IPT_DIV">
      <input type="text" title="input hex numbers" />
    </div>
    <svg
      id="SVG"
      width="1400"
      height="720"
      viewBox="0 0 1400 720"
      xmlns="http://www.w3.org/2000/svg"
    ></svg>
  </body>

  <script type="module" src="./tpo2n.mjs"></script>
  <script type="module" src="./tpo2p.mjs"></script>
  <script type="module">
    import { prev } from "./tpo2p.mjs";
    import { next } from "./tpo2n.mjs";
    const prevStr = (str) => prev(str.split("").map((e) => +e)).join("");
    const nextStr = (str) => next(str.split("").map((e) => +e)).join("");

    const HIDDEN_LENGTH = 1 << 13;
    const SVG = document.getElementById("SVG");
    // Create SVG Element
    const createSVGelm = (type = "rect") =>
      document.createElementNS("http://www.w3.org/2000/svg", type);

    const IPT_DIV = document.getElementById("IPT_DIV");

    // Center of SVG
    const PAD_LENGTH = (() => {
      const svgBounds = SVG.getBoundingClientRect();
      let iptDivBounds = IPT_DIV.getBoundingClientRect();
      IPT_DIV.style.top = `${svgBounds.top}px`;
      IPT_DIV.style.left = `${
        svgBounds.left + (svgBounds.width - iptDivBounds.width) / 2
      }px`;

      iptDivBounds = IPT_DIV.getBoundingClientRect();
      return {
        width: svgBounds.width,
        height: svgBounds.height,
        top: iptDivBounds.top - svgBounds.top,
        left: svgBounds.width / 2,
      };
    })();

    // Input Control
    const IPT = IPT_DIV.firstElementChild;

    IPT.addEventListener("blur", () => {
      // hide input div
      IPT_DIV.style.visibility = "hidden";
    });

    // Check input nums is valid or not
    const REG = /^[0-5]+$/;

    // While change input value
    const iptChange = (force = false) => {
      if (!Controler.ROOT_NUMS) return null;
      if (!force && IPT.value === Controler.ROOT_NUMS.nums) return null;
      if (!REG.test(IPT.value)) {
        IPT.value = Controler.ROOT_NUMS.nums || "0";
        alert("Error input");
        return null;
      }
      // update nums
      Controler.ROOT_NUMS.nums = IPT.value;
      Controler.clearSVG();
      Controler.setRoot();
      Controler.getTree();
      Controler.drawTree();
      console.debug("Tree length", SVG.childNodes.length);
    };
    IPT.addEventListener("change",()=> iptChange(false));

    const Controler = {
      PAD_Y: 60,
      ROOT_NUMS: null,
      CUR_DEPTH: 0,
      MAX_ROOT:4,
      MAX_LEAF: 4,

      // clearSVG: () => {
      //   // SVG.innerHTML = "";
      //   // SVG.replaceChildren();
      //   let count = 0;
      //   for (let i = 0; i < SVG.childNodes.length; i++) {
      //     let node = SVG.childNodes[i];
      //     if (node.obj === Controler.ROOT_NUMS) continue;
      //     if (node.obj) node.obj.clear(), i--, count++;
      //   }
      //   console.log("delet node", count);
      //   Controler.ROOT_NUMS ? SVG.appendChild(Controler.ROOT_NUMS.elm) : 0;
      // },

      setRoot: () => {
        if (!REG.test(Controler.ROOT_NUMS?.nums)) return null;

        let nNums = Controler.ROOT_NUMS.nums;
        let i = 1;
        for (; i <= Controler.MAX_ROOT; i++) {
          nNums = nextStr(nNums);
          // console.debug("setRoot", nNums);
          if (nNums.length === 1) break;
        }
        // Controler.ROOT_NUMS = new SVGNumbers({
        //   nums: nNums,
        //   x: Controler.ROOT_NUMS.x,
        //   y: Controler.ROOT_NUMS.y,
        // });
        Controler.ROOT_NUMS.nums = nNums;
        Controler.CUR_DEPTH = i;
      },

      clearSVG: () => {
        SVG.replaceChildren();
      },

      getTree: () => {
        if (!Controler.ROOT_NUMS?.nums) return null;
        
        Controler.ROOT_NUMS.children.length = 0;
        let count = 1;

        const iter = (node, level) => {
          if (level >= Controler.MAX_LEAF + Controler.CUR_DEPTH) return null;

          let pNums = prevStr(node.nums);

          if (node.end === "2") {
            // console.debug("getTree", pNums);
            let c0 = new SVGNumbers({
              nums: pNums.slice(0, -1),
              x: HIDDEN_LENGTH,
              y: HIDDEN_LENGTH,
            });

            count++;

            c0.parent = node;
            node.children.push(c0);
            // recursion
            iter(c0, level + 1);
          }

          let c1 = new SVGNumbers({
            nums: pNums,
            x: HIDDEN_LENGTH,
            y: HIDDEN_LENGTH,
          });

          count++;

          c1.parent = node;
          node.children.push(c1);
          // recursion
          iter(c1, level + 1);
        };

        iter(Controler.ROOT_NUMS, 0);

        console.debug("New Tree", count);
      },

      drawTree: () => {
        if (Controler.ROOT_NUMS === null) return null;
        SVG.appendChild(Controler.ROOT_NUMS.elm);

        const iter = (node, level) => {
          if (node.children.length === 0) return null;

          let [c0, c1] = [...node.children];

          c0.y = node.y + Controler.PAD_Y;

          if (!!c1) {
            c0.x = node.x - (PAD_LENGTH.width >> level);
            c1.y = node.y + Controler.PAD_Y;
            c1.x = node.x + (PAD_LENGTH.width >> level);
            SVG.appendChild(c1.elm);
            c1.link();
            iter(c1, level + 1);
          } else {
            c0.x = node.x;
          }

          SVG.appendChild(c0.elm);
          c0.link();
          iter(c0, level + 1);
        };

        iter(Controler.ROOT_NUMS, 2);

        /*
        const auxiliaryLine = (node) => {
          let lv = createSVGelm("line");
          lv.setAttribute("x1", PAD_LENGTH.width / 2);
          lv.setAttribute("y1", 0);
          lv.setAttribute("x2", PAD_LENGTH.width / 2);
          lv.setAttribute("y2", PAD_LENGTH.height);
          let lh = createSVGelm("line");
          lh.setAttribute("x1", 0);
          lh.setAttribute("y1", PAD_LENGTH.height / 2);
          lh.setAttribute("x2", PAD_LENGTH.width);
          lh.setAttribute("y2", PAD_LENGTH.height / 2);
          SVG.appendChild(lv);
          SVG.appendChild(lh);
        };
        auxiliaryLine();
        */
      },
    };

    class SVGNumbers {
      parent = null;
      children = [];

      constructor({ nums = "0", x = 100, y = 100 }) {
        this._nums = nums;
        this.elm = createSVGelm("text");
        this.elm.obj = this;

        this.elm.setAttribute("x", x);
        this.elm.setAttribute("y", y);
        this.elm.textContent = nums;
        // Append to SVG
        SVG.appendChild(this.elm);

        // Double click event, omit single click
        this.elm.addEventListener("dblclick", (evn) => {
          if (Controler.ROOT_NUMS === this) {
            IPT_DIV.style.visibility = "visible";
            return null;
          }

          Controler.ROOT_NUMS = this;
          IPT_DIV.style.visibility = "hidden";
          let br = this.elm.getBoundingClientRect();
          this.x = PAD_LENGTH.left;
          this.y = br.height * 0.6;

          Controler.clearSVG();
          IPT.value = this.nums;
          iptChange(true);
        });
      }

      get x() {
        return parseInt(this.elm.getAttribute("x")) || 0;
      }

      set x(x) {
        if (x === this.x) return null;
        this.elm.setAttribute("x", x);
      }

      get y() {
        return parseInt(this.elm.getAttribute("y")) || 0;
      }

      set y(y) {
        if (y === this.y) return null;
        this.elm.setAttribute("y", y);
      }

      get nums() {
        return this._nums;
      }

      set nums(n) {
        if (this._nums === n) return null;
        this._nums = n;
        this.elm.textContent = n;
      }

      get end() {
        return this._nums?.charAt(this._nums?.length - 1);
      }

      get rect() {
        return this.elm.getBoundingClientRect();
      }

      link() {
        if (!this.parent) return null;
        this.line = createSVGelm("line");
        let [rect0, rect1] = [this.rect, this.parent.rect];
        this.line.setAttribute("x1", this.x);
        this.line.setAttribute("y1", this.y - rect0.height);
        this.line.setAttribute("x2", this.parent.x);
        this.line.setAttribute("y2", this.parent.y + 10);
        SVG.appendChild(this.line);
      }

      clear() {
        this.line ? SVG.removeChild(this.line) : null;
        delete this.line;
        SVG.removeChild(this.elm);
        delete this.elm.obj;
        delete this.elm;
        delete this.parent;
        this.children.length = 0;
        delete this.children;
        delete this;
      }
    }

    // test unit
    (() => {
      let n1 = new SVGNumbers({ nums: "121", x: 0, y: 0 });
      IPT.value = "121";
      n1.elm.dispatchEvent(new MouseEvent("dblclick"));
      // iptChange(true);
    })();
  </script>
</html>
